#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>

#define MAXSIZE 128
#define MAXARGS 32
// 命令行参数表
char *gargv[MAXARGS];
int gargc = 0;

// shell自己的环境变量表
char *genv[MAXARGS];
int genvc = 0;

const char *gseq = " ";

// 我们shell的当前工作路径
char cwd[MAXSIZE];

// 记录最近一次进程的退出码
int lastcode = 0;

const char* GetUserName()
{
    char* name = getenv("USER");
    if (name == NULL)
        return "None";
    return name;
}

const char* GetHostName()
{
    char* hostname = getenv("HOSTNAME");
    if (hostname == NULL)
        return "None";
    return hostname;
}

const char* GetPwd()
{
    char* pwd = getenv("PWD");
    if (pwd == NULL)
        return "None";
    return pwd;

}

const std::string rfindDir(const std::string& p)
{
    if (p == "/")
        return p;
    else 
    {
        const std::string psep = "/";
        auto pos = p.rfind(psep);
        if (pos == std::string::npos)
        {
            return std::string();
        }
        else 
        {
            return p.substr(pos+1);
        }
    }
}

extern char **environ;

void LoadEnv()
{
    for(; environ[genvc]; genvc++)
    {
        genv[genvc] = (char*)malloc(sizeof(char) * 4096);
        strcpy(genv[genvc], environ[genvc]);
    }
    genv[genvc] = NULL;

    printf("Load Env...\n");
    for (int j = 0; genv[j]; j++)
    {
        printf("genv[%d]: %s\n", j, genv[j]);
    }
}

void PrintCommandLine()
{
    // 用户名 主机名 当前路径
    printf("[%s@%s %s]# ", GetUserName(), GetHostName(), rfindDir(GetPwd()).c_str());
    fflush(stdout);
}

int GetCommandLine(char *commandline, int size)
{
    fgets(commandline, size, stdin);
    commandline[strlen(commandline) - 1] = '\0';

    return strlen(commandline);
}

int ParseCommand(char *commandline)
{
    gargc = 0;
    memset(gargv, 0, sizeof(gargv));

    gargv[0] = strtok(commandline, gseq);
    while (gargv[++gargc] = strtok(NULL, gseq));

    //printf("gargc: %d\n", gargc);
    //int i = 0;
    //for (; gargv[i]; i++)
    //{
    //    printf("gargv[%d]: %s\n", i, gargv[i]);
    //}

    return gargc;
}

// return val
// 0 : 不是内建命令
// 1 : 是内建命令 && 执行命令
int CheckBuiltInExecute()
{
    if (strcmp(gargv[0], "cd") == 0)
    {
        if (gargc == 2)
        {
            // 1.修改内核路径
            chdir(gargv[1]);
            // 2.修改环境变量
            char pwd[1024];
            getcwd(pwd, sizeof(pwd));
            snprintf(cwd, sizeof(cwd), "PWD=%s", pwd);
            putenv(cwd);
            lastcode = 0;
        }
        return 1;
    }
    else if (strcmp(gargv[0], "echo") == 0)
    {
        if (gargc == 2)
        {
            if (gargv[1][0] == '$')
            {
                if (strcmp(gargv[1]+1, "?") == 0)
                {
                    printf("lastcode: %d\n", lastcode);
                }
                else if (strcmp(gargv[1]+1, "PATH") == 0)
                {
                    printf("PATH=%s\n", getenv("PATH"));
                }
            }

            lastcode = 0;
        }

        return 1;
    }

    return 0;
}

int ExecuteCommand()
{
    pid_t id = fork();
    if (id < 0)
        return -1;
    else if (id == 0)
    {
        // child
        execvp(gargv[0], gargv);
        exit(1);
    }
    else 
    {
        // parent
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            //printf("wait success!\n");
            //to do
            lastcode = WEXITSTATUS(status);
        }
    }

    return 0;
}

int main()
{
    char command_line[MAXSIZE] = {0};
    // 0.从配置文件中加载环境变量到环境变量表
    LoadEnv();
    while(1)
    {
        // 1.打印字符串
        PrintCommandLine();
        // 2.获取用户输入
        if (0 == GetCommandLine(command_line, sizeof(command_line)))
                continue;
        
        //printf("%s\n", command_line);
        // 3.解析命令
        ParseCommand(command_line);
        
        // 4.判断是否为内建命令，是则父进程执行
        if (CheckBuiltInExecute())
        {
            continue;
        }

        // 5.子进程执行命令
        ExecuteCommand();
    }
    return 0;
}
